package no.group.gowther.util;

import no.group.gowther.exception.*;
import no.group.gowther.service.dto.param.Page;
import org.springframework.util.FileSystemUtils;

import java.io.File;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.atomic.LongAccumulator;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * @author ayatsuji
 * @date 2022-04-17 22:02
 * @since
 */
public class FileUtils {
    public static String extractExtension(String filename) {
        final int dotIndex = filename.lastIndexOf(".");
        if (dotIndex == -1) return "";
        return filename.substring(dotIndex + 1);
    }

    private static final String[] FILE_SIZE_UNIT = {"B", "K", "M", "G", "T"};
    public static final String EMPTY_SIZE = "0";
    public static String getFileSize(Path path) throws IOException {
        if (Files.isRegularFile(path)) {
            return calFileSizeH(Files.size(path));
        }
        return EMPTY_SIZE;
    }

    private static String calFileSizeH(double fileByteSize) {
        int ind = 0;
        while (fileByteSize >= 1024) {
            ind++;
            fileByteSize /= 1024;
        }
        return oneDecimal(fileByteSize) + FILE_SIZE_UNIT[ind];
    }

    private static String oneDecimal(double num) {
        return String.format("%.1f", num);
    }

    public static long fileCount(Path path) throws IOException {
        return fileCount(path, f -> true);
    }

    public static long fileCount(Path path, Predicate<Path> filter) throws IOException {
        return Files.list(path).filter(filter).count();
    }

    public static List<Path> listFileBy(Path dirPath, Predicate<Path> filter) throws IOException {
        return Files.list(dirPath)
                .sorted(Comparator.comparingInt((Path p) -> p.getFileName().toString().length())
                        .thenComparing(p -> p.getFileName().toString()))
                .filter(filter)
                .collect(Collectors.toList());
    }

    public static List<Path> listFileBy(Path dirPath, Predicate<Path> filter, Page page) throws IOException {
        return Files.list(dirPath)
                .sorted(Comparator.comparingInt((Path p) -> p.getFileName().toString().length())
                        .thenComparing(p -> p.getFileName().toString()))
                .filter(filter)
                .skip(page.toPageOffset())
                .limit(page.getPageSize())
                .collect(Collectors.toList());
    }

    public static List<Path> listRoot() {
        File[] files = File.listRoots();
        return Arrays.stream(files).map(File::toPath).collect(Collectors.toList());
    }

    public static void checkDiskCapacityEnough(Path filePath, Path dirPath) throws DiskCapacityNotEnoughException, IOException {
        final long usableSpace = getUsableSpace(dirPath);
        long fileSize;
        if (Files.isDirectory(filePath)) {
            fileSize = calDirTotalSize(filePath);
        } else {
            fileSize = Files.size(filePath);
        }
        if (usableSpace <= fileSize) {
            throw new DiskCapacityNotEnoughException(String.format("目标位置[%s]磁盘空间不足", dirPath));
        }
    }

    public static long getUsableSpace(Path filePath) throws IOException {
        return getFileStore(filePath).getUsableSpace();
    }

    private static long calDirTotalSize(Path dirPath) throws CalDirTotalSizeException {
        LongAccumulator sum = new LongAccumulator(Long::sum, 0);
        try {
            Files.walkFileTree(dirPath, new SimpleFileVisitor<>() {
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    sum.accumulate(attrs.size());
                    return FileVisitResult.CONTINUE;
                }
            });
            return sum.get();
        } catch (IOException e) {
            throw new CalDirTotalSizeException(String.format("统计目录[%s]大小失败", dirPath), e);
        }
    }

    private static FileStore getFileStore(Path filePath) throws FileDiskStoreNotFoundException {
        final String diskSymbol = getDiskSymbol(filePath);
        for (FileStore fileStore : filePath.getFileSystem().getFileStores()) {
            if (fileStore.toString().toUpperCase().contains(diskSymbol.toUpperCase())) {
                return fileStore;
            }
        }
        throw new FileDiskStoreNotFoundException(String.format("找不到文件[%s]对应的磁盘分区", filePath));
    }

    private static String getDiskSymbol(Path filePath) {
        return filePath.getRoot().toString().substring(0, filePath.getRoot().toString().indexOf("\\"));
    }

    public static void deleteFile(Path filePath) throws IOException {
        if (Files.isDirectory(filePath)) {
            FileSystemUtils.deleteRecursively(filePath);
            return;
        }
        Files.delete(filePath);
    }

    public static void createDir(Path dirPath) throws IOException {
        Files.createDirectories(dirPath);
    }
}
